﻿using CTP.CRM.Core.OTel.Options;
using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Instrumentation.AspNetCore;
using OpenTelemetry.Instrumentation.EntityFrameworkCore;
using OpenTelemetry.Instrumentation.Http;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Unicode;

namespace CTP.CRM.Core.OTel
{
    //
    // 摘要:
    //     Extension methods for setting up OpenTelemetry in an Microsoft.Extensions.DependencyInjection.IServiceCollection.
    public static class OTelServiceCollectionExtensions
    {
        //
        // 摘要:
        //     Adds OTel to the specified Microsoft.AspNetCore.Builder.IApplicationBuilder.
        //
        //
        //     将 OTel 添加到指定的 IApplicationBuilder。
        //
        // 参数:
        //   app:
        public static void UseOTel([NotNull] this IApplicationBuilder app)
        {
            app.Use(async delegate (HttpContext context, Func<Task> next)
            {
                context.Response.Headers.TryAdd("X-Request-Id", Activity.Current?.TraceId.ToString());
                HttpRequest request = context.Request;
                if ((ActivityHelper.OTelOptions?.EnableAspNetCore ?? false) && request.ContentType != null && request.ContentType.Contains("application/json"))
                {
                    using MemoryStream newRequestBody = new MemoryStream();
                    await context.Request.Body.CopyToAsync(newRequestBody);
                    newRequestBody.Seek(0L, SeekOrigin.Begin);
                    using StreamReader reader = new StreamReader(newRequestBody, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, 1024, leaveOpen: true);
                    string requestBodyText = await reader.ReadToEndAsync();
                    Activity.Current?.SetTag("http.request.body", requestBodyText);
                    Activity.Current?.SetTag("http.request.headers.authorization", request?.Headers?.Authorization);
                    newRequestBody.Seek(0L, SeekOrigin.Begin);
                    context.Request.Body = newRequestBody;
                    await next();
                }
                else
                {
                    await next();
                }
            });
        }

        //
        // 摘要:
        //     Adds OTel to the specified Microsoft.AspNetCore.Builder.WebApplicationBuilder.
        //
        //
        //     将 OTel 添加到指定的 WebApplicationBuilder。
        //
        // 参数:
        //   appBuilder:
        //
        //   tracerProvider:
        //
        //   meterProvider:
        public static WebApplicationBuilder AddOTel(this WebApplicationBuilder appBuilder, Action<TracerProviderBuilder> tracerProvider = null, Action<MeterProviderBuilder> meterProvider = null)
        {
            return appBuilder.AddOTel(null, tracerProvider, meterProvider);
        }

        //
        // 摘要:
        //     Adds OTel to the specified Microsoft.AspNetCore.Builder.WebApplicationBuilder.
        //
        //
        //     将 OTel 添加到指定的 WebApplicationBuilder。
        //
        // 参数:
        //   appBuilder:
        //
        //   configure:
        //     OTel options.
        //
        //     OTel 配置选项。
        //
        //   tracerProvider:
        //     tracer provider.
        //
        //     链路提供者。
        //
        //   meterProvider:
        //     meter provider.
        //
        //     指标提供者。
        public static WebApplicationBuilder AddOTel(this WebApplicationBuilder appBuilder, Action<OTelOptions> configure, Action<TracerProviderBuilder> tracerProvider = null, Action<MeterProviderBuilder> meterProvider = null)
        {
            IConfigurationSection section = appBuilder.Configuration.GetSection("OTel");
            OTelOptions oTelOptions = new OTelOptions
            {
                EnableAspNetCore = true,
                EnableHttpClient = true,
                EnableSql = true
            };
            section.Bind(oTelOptions);
            configure?.Invoke(oTelOptions);
            OTelOptions oTelOptions2 = oTelOptions;
            if (oTelOptions2.Serializer == null)
            {
                Func<object, string> func2 = (oTelOptions2.Serializer = delegate (object obj)
                {
                    try
                    {
                        JsonSerializerOptions options = new JsonSerializerOptions
                        {
                            Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),
                            ReferenceHandler = ReferenceHandler.Preserve,
                            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
                        };
                        return JsonSerializer.Serialize(obj, options);
                    }
                    catch (Exception)
                    {
                        return obj?.ToString();
                    }
                });
            }

            appBuilder.Services.AddOTelCore(oTelOptions, tracerProvider, meterProvider);
            return appBuilder;
        }

        //
        // 摘要:
        //     Adds OTel to the specified Microsoft.Extensions.DependencyInjection.IServiceCollection.
        //
        //
        //     将 OTel 添加到指定的 IServiceCollection。
        //
        // 参数:
        //   services:
        //
        //   configure:
        //
        //   tracerProvider:
        //
        //   meterProvider:
        public static IServiceCollection AddOTel(this IServiceCollection services, Action<OTelOptions> configure, Action<TracerProviderBuilder> tracerProvider = null, Action<MeterProviderBuilder> meterProvider = null)
        {
            OTelOptions oTelOptions = new OTelOptions();
            configure?.Invoke(oTelOptions);
            OTelOptions oTelOptions2 = oTelOptions;
            if (oTelOptions2.Serializer == null)
            {
                Func<object, string> func2 = (oTelOptions2.Serializer = delegate (object obj)
                {
                    try
                    {
                        JsonSerializerOptions options = new JsonSerializerOptions
                        {
                            Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),
                            ReferenceHandler = ReferenceHandler.Preserve,
                            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
                        };
                        return JsonSerializer.Serialize(obj, options);
                    }
                    catch (Exception)
                    {
                        return obj?.ToString();
                    }
                });
            }

            return services.AddOTelCore(oTelOptions, tracerProvider, meterProvider);
        }

        //
        // 摘要:
        //     Adds OTel to the specified Microsoft.Extensions.DependencyInjection.IServiceCollection.
        //
        //
        //     将 OTel 添加到指定的 IServiceCollection。
        //
        // 参数:
        //   services:
        //
        //   options:
        //     OTel options.
        //
        //     OTel 配置选项。
        //
        //   tracerProvider:
        //     tracer provider.
        //
        //     链路提供者。
        //
        //   meterProvider:
        //     meter provider.
        //
        //     指标提供者。
        private static IServiceCollection AddOTelCore(this IServiceCollection services, OTelOptions options, Action<TracerProviderBuilder> tracerProvider = null, Action<MeterProviderBuilder> meterProvider = null)
        {
            ArgumentNullException.ThrowIfNull(options, "options");
            if (string.IsNullOrEmpty(options.ServiceName))
            {
                throw new ArgumentException("ServiceName不能为空", "options");
            }

            if (string.IsNullOrEmpty(options.ServiceVersion))
            {
                throw new ArgumentException("ServiceVersion不能为空", "options");
            }

            if (options.Serializer == null)
            {
                throw new ArgumentException("Serializer不能为空", "options");
            }

            Uri endpoint = (string.IsNullOrEmpty(options.ExporterUrl) ? null : new Uri(options.ExporterUrl));
            OtlpExportProtocol protocol = OtlpExportProtocol.Grpc;
            if (options.ExporterProtocol == "http/protobuf")
            {
                protocol = OtlpExportProtocol.HttpProtobuf;
            }

            ActivityHelper.InitActivitySource(options);
            services.AddSingleton(TracerProvider.Default.GetTracer(options.ServiceName));
            OpenTelemetryBuilder openTelemetryBuilder = services.AddOpenTelemetry().ConfigureResource(delegate (ResourceBuilder resource)
            {
                resource.AddService(options.ServiceName);
            }).WithTracing(delegate (TracerProviderBuilder tracing)
            {
                switch (options.SamplerType)
                {
                    case SamplerType.AlwaysOn:
                        tracing.SetSampler(new AlwaysOnSampler());
                        break;
                    case SamplerType.AlwaysOff:
                        tracing.SetSampler(new AlwaysOffSampler());
                        break;
                    case SamplerType.ParentBasedSampler:
                        {
                            SamplerType childBasedSampler = options.ChildBasedSampler;
                            if (1 == 0)
                            {
                            }

                            Sampler sampler = childBasedSampler switch
                            {
                                SamplerType.AlwaysOn => new AlwaysOnSampler(),
                                SamplerType.AlwaysOff => new AlwaysOffSampler(),
                                SamplerType.TraceIdRatio => new TraceIdRatioBasedSampler(options.SamplerProbability),
                                _ => new AlwaysOnSampler(),
                            };
                            if (1 == 0)
                            {
                            }

                            Sampler rootSampler = sampler;
                            tracing.SetSampler(new ParentBasedSampler(rootSampler));
                            break;
                        }
                    case SamplerType.TraceIdRatio:
                        tracing.SetSampler(new TraceIdRatioBasedSampler(options.SamplerProbability));
                        break;
                    default:
                        tracing.SetSampler(new AlwaysOnSampler());
                        break;
                }

                tracing.AddSource(options.ServiceName);
                if (options.EnableAspNetCore)
                {
                    tracing.AddAspNetCoreInstrumentation(delegate (AspNetCoreTraceInstrumentationOptions o)
                    {
                        o.RecordException = false;
                        o.EnrichWithException = delegate (Activity activity, Exception exception)
                        {
                            activity.SetTag("exception.type", exception.GetType().Name);
                            activity.SetTag("exception.message", exception.Message);
                        };
                        o.Filter = (HttpContext httpContent) => !httpContent.Request.Path.StartsWithSegments("/swagger");
                    });
                }

                if (options.EnableHttpClient)
                {
                    tracing.AddHttpClientInstrumentation(delegate (HttpClientTraceInstrumentationOptions o)
                    {
                        o.RecordException = true;
                        o.EnrichWithHttpResponseMessage = delegate (Activity activity, HttpResponseMessage message)
                        {
                            activity.SetTag("http.status_code", message.StatusCode);
                        };
                        o.EnrichWithException = delegate (Activity activity, Exception exception)
                        {
                            activity.SetTag("exception.type", exception.GetType().Name);
                            activity.SetTag("exception.message", exception.Message);
                        };
                        o.FilterHttpRequestMessage = (HttpRequestMessage httpContent) => httpContent.RequestUri != null && (!httpContent.RequestUri.ToString().Contains("elasticsearch") || !httpContent.RequestUri.ToString().Contains("kibana") || !httpContent.RequestUri.ToString().Contains("apm-server") || !httpContent.RequestUri.ToString().Contains("apm-agent-dotnet") || !httpContent.RequestUri.ToString().Contains("apollo-server"));
                    });
                }

                if (options.EnableSql)
                {
                    tracing.AddEntityFrameworkCoreInstrumentation(delegate (EntityFrameworkInstrumentationOptions o)
                    {
                        o.EnrichWithIDbCommand = delegate (Activity activity, IDbCommand command)
                        {
                            activity.DisplayName = "EF执行SQL";
                            activity.SetTag("sql", command?.CommandText);
                            if (options.EnableSqlParameters && command?.Parameters != null)
                            {
                                Dictionary<string, object> dictionary = new Dictionary<string, object>();
                                foreach (DbParameter parameter in command.Parameters)
                                {
                                    string parameterName = parameter.ParameterName;
                                    object value = parameter.Value;
                                    dictionary.Add(parameterName, value);
                                }

                                activity.SetTag("sqlParameters", options.Serializer(dictionary));
                            }
                        };
                    });
                }

                if (options.EnableCap)
                {
                    tracing.AddCapInstrumentation();
                }

                Uri uri3 = endpoint;
                Uri uri4 = uri3;
                if ((object)uri4 != null)
                {
                    tracing.AddOtlpExporter(delegate (OtlpExporterOptions opt)
                    {
                        opt.TimeoutMilliseconds = options.ExporterTimeoutSeconds * 1000;
                        opt.Protocol = protocol;
                        opt.Endpoint = endpoint;
                    });
                }
                else if (options.EnableConsoleExporter)
                {
                    tracing.AddConsoleExporter();
                }

                tracerProvider?.Invoke(tracing);
            })
                .WithMetrics(delegate (MeterProviderBuilder metrics)
                {
                    if (options.EnableAspNetCore)
                    {
                        metrics.AddAspNetCoreInstrumentation();
                    }

                    if (options.EnableHttpClient)
                    {
                        metrics.AddHttpClientInstrumentation();
                    }

                    Uri uri = endpoint;
                    Uri uri2 = uri;
                    if ((object)uri2 != null)
                    {
                        metrics.AddOtlpExporter(delegate (OtlpExporterOptions opt)
                        {
                            opt.TimeoutMilliseconds = options.ExporterTimeoutSeconds * 1000;
                            opt.Protocol = protocol;
                            opt.Endpoint = endpoint;
                        });
                    }
                    else if (options.EnableConsoleExporter)
                    {
                        metrics.AddConsoleExporter();
                    }

                    meterProvider?.Invoke(metrics);
                });
            return services;
        }

        //
        // 摘要:
        //     读取请求体
        //
        // 参数:
        //   request:
        public static async Task<string> ReadRequestBodyAsStringAsync(HttpRequestMessage request)
        {
            if (request.Content == null)
            {
                return null;
            }

            using MemoryStream ms = new MemoryStream();
            await request.Content.CopyToAsync(ms);
            ms.Position = 0L;
            using StreamReader reader = new StreamReader(ms);
            return await reader.ReadToEndAsync();
        }

        private static async Task<string> ReadRequestBody(HttpRequest request)
        {
            request.Body.Seek(0L, SeekOrigin.Begin);
            using StreamReader reader = new StreamReader(request.Body);
            string body = await reader.ReadToEndAsync();
            request.Body.Seek(0L, SeekOrigin.Begin);
            return body;
        }
    }
}
